package org.mmtk.plan.colormark;

import org.mmtk.plan.StopTheWorld;
import org.mmtk.plan.Trace;
import org.mmtk.plan.TransitiveClosure;
import org.mmtk.plan.scale.ScaleMarkSweepSpace;
import org.mmtk.plan.scale.colors.ColorColorsOption;
import org.mmtk.plan.scale.colors.ColorHelpersOption;
import org.mmtk.plan.scale.colors.MaxColorOption;
import org.mmtk.policy.Space;
import org.mmtk.utility.Log;
import org.mmtk.utility.heap.VMRequest;
import org.mmtk.utility.options.Options;
import org.mmtk.vm.Lock;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;

@Uninterruptible
public class CM extends StopTheWorld {

	/****************************************************************************
	 * Class variables
	 */
	public static final ScaleMarkSweepSpace cmSpace = new ScaleMarkSweepSpace("ms",
			DEFAULT_POLL_FREQUENCY, VMRequest.create());
	public static final int CM_SPACE = cmSpace.getDescriptor();

	public static final int SCAN_MARK = 0;
	public static final int SCAN_HELP = 1;

	/****************************************************************************
	 * Instance variables
	 */
	public final Trace regularTrace = new Trace(metaDataSpace);
	public static int collectorThreads;
	public static int helperThreads;
	private static int helperIndex;
	private static Lock helperLock = VM.newLock("helperLock");
	public static CMHelper[] helpers;
	private CMSweeper sweeper = new CMSweeper();
	private static int tracersDone;
	private static Lock logLock = VM.newLock("logLock");
	
	public CM() {
		//disable reference types scan
		Options.noReferenceTypes.setValue(true);
	    Options.noFinalizer.setValue(true);
	    Options.printPhaseStats.setValue(true);
	    
	    //read helpers option
	    Options.scaleColorHelpers = new ColorHelpersOption();
	    Options.scaleColorColors = new ColorColorsOption();
	    Options.maxColorOption = new MaxColorOption();
	    //can not use logging here yet
	}
	
	/*****************************************************************************
	 * Collection
	 */

	/**
	 * Perform a (global) collection phase.
	 * 
	 * @param phaseId
	 *            Collection phase to execute.
	 */
	@Inline
	@Override
	public void collectionPhase(short phaseId) {
		if (phaseId == PREPARE) {
			super.collectionPhase(phaseId);
			regularTrace.prepare();
			cmSpace.prepare(true, true);
			helperIndex = -1;
			tracersDone = 0;
			if (Options.verbose.getValue() >= 1) {
				Log.write("getMarkState(): ");Log.writeln(cmSpace.getMarkState());
				Log.write("getAllocState(): ");Log.writeln(cmSpace.getAllocState());
			}
			return;
		}

		if (phaseId == CLOSURE) {
			regularTrace.prepare();
			return;
		}
		
		if (phaseId == RELEASE) {
			markLiveColors();
			regularTrace.release();
			cmSpace.release(sweeper);
			//cmSpace.sweepCells(sweeper);
			super.collectionPhase(phaseId);
			if (Options.verbose.getValue() >= 1) {
				Log.write("Global release done:\n");
			}
			dumpHelpers();
			return;
		}

		super.collectionPhase(phaseId);
	}
	
	public static CMHelper getHelper(boolean primary) {
		CMHelper result = null;
		
		if(primary) {
			return result;
		}
		
		helperLock.acquire();
		
		if (helperIndex < helperThreads) {
			helperIndex++;
			if (helperIndex < helperThreads) {
				result = helpers[helperIndex];
			} 
		}
		
		helperLock.release();
		return result;
	}

	/*****************************************************************************
	 * Accounting
	 */

	/**
	 * Return the number of pages reserved for use given the pending allocation.
	 * The superclass accounts for its spaces, we just augment this with the
	 * mark-sweep space's contribution.
	 * 
	 * @return The number of pages reserved given the pending allocation,
	 *         excluding space reserved for copying.
	 */
	@Override
	public int getPagesUsed() {
		return (cmSpace.reservedPages() + super.getPagesUsed());
	}

	/**
	 * Calculate the number of pages a collection is required to free to satisfy
	 * outstanding allocation requests.
	 * 
	 * @return the number of pages a collection is required to free to satisfy
	 *         outstanding allocation requests.
	 */
	@Override
	public int getPagesRequired() {
		return super.getPagesRequired() + cmSpace.requiredPages();
	}

	/*****************************************************************************
	 * Miscellaneous
	 */

	/**
	 * @see org.mmtk.plan.Plan#willNeverMove
	 * 
	 * @param object
	 *            Object in question
	 * @return True if the object will never move
	 */
	@Override
	public boolean willNeverMove(ObjectReference object) {
		if (Space.isInSpace(CM_SPACE, object))
			return true;
		return super.willNeverMove(object);
	}

	/**
	 * Register specialized methods.
	 */
	@Interruptible
	@Override
	protected void registerSpecializedMethods() {
		TransitiveClosure.registerSpecializedScan(SCAN_MARK, CMTraceLocal.class);
		TransitiveClosure.registerSpecializedScan(SCAN_HELP, CMHelperTraceLocal.class);
		super.registerSpecializedMethods();
	}
	
	@Interruptible
	@Override
	public void postBoot() {
		super.postBoot();
		
		collectorThreads = VM.activePlan.collectorCount();
		//helperThreads = collectorThreads/2;
		helperThreads = Options.scaleColorHelpers.getValue();
		if(collectorThreads - helperThreads < 1) {
			helperThreads = collectorThreads - 1;
		}
		helpers = new CMHelper[helperThreads];
		for(int i = 0; i < helperThreads; i++) {
	    	helpers[i] = new CMHelper(helperThreads);
	    }	
		if(helperThreads == 0) {
			sweeper = null;
		}
		
		VM.config.printConfig();
		Log.write("Options.colorMarkSweepMarkBits.getValue()=");Log.writeln(Options.colorMarkSweepMarkBits.getValue());
		Log.write("UNLOGGED_BIT: ");Log.writeln(ScaleMarkSweepSpace.UNLOGGED_BIT);
		Log.write("MARK_COUNT_INCREMENT: ");Log.writeln(ScaleMarkSweepSpace.MARK_COUNT_INCREMENT);
		Log.write("MARK_COUNT_MASK: ");Log.writeln(ScaleMarkSweepSpace.MARK_COUNT_MASK);
		Log.write("MARK_BITS_MASK: ");Log.writeln(ScaleMarkSweepSpace.MARK_BITS_MASK);
		Log.write("getMarkState(): ");Log.writeln(cmSpace.getMarkState());
		Log.write("getAllocState(): ");Log.writeln(cmSpace.getAllocState());
		Log.write("collectorThreads: ");Log.writeln(collectorThreads);
		Log.write("helperThreads: ");Log.writeln(helperThreads);	
		dumpHelpers();
		Log.flush();
	}
	
	public static CMHelper getHelper(ObjectReference color) {
		CMHelper result = null;
		
		for(int i = 0; i < helperThreads; i++) {
			if(helpers[i].getColor() == color) {
				result = helpers[i];
				break;
			}
		}
		
		return result;
	}
	
	private void markLiveColors() {
		boolean done = false;

		while (!done) {
			done = true;

			for (int i = 0; i < helperThreads; i++) {
				CMHelper helper = helpers[i];
				if (!helper.isLive()) {
					continue;
				}
				for (int j = 0; j < helperThreads; j++) {
					if (j == i) {
						continue;
					}
					ObjectReference color = helpers[j].getColor();
					if (!helper.hasColor(color)) {
						continue;
					}
					if (helpers[j].markLive()) {
						done = false;
					}
				}
			}
		}
	}
	
	private void dumpHelpers() {
		for (int i = 0; i < helperThreads; i++) {
			Log.write("Helper number ");
			Log.write(i);
			CMHelper helper = helpers[i];
			ObjectReference color = helper.getColor();
			Log.write(", color=");
			Log.write(color);
			if (helper.isLive()) {
				Log.write(", live");
			} else {
				Log.write(", not live");
			}
			long roots = helper.getRoots();
			Log.write(", roots=");
			Log.write(roots);
			long traced = helper.getTraced();
			Log.write(", traced=");
			Log.write(traced);
			long live = helper.getLive();
			Log.write(", made live=");
			Log.write(live);
			long colored = helper.getColored();
			Log.write(", colored=");
			Log.write(colored);
			Colors colors = helper.getDependentColors();
			Log.write(", colors=");
			colors.dump();
			Log.write("\n");
			Log.flush();
		}
	}
	
	public static void reportTraceDone() {
		helperLock.acquire();
		tracersDone++;
		helperLock.release();
	}
	
	public static boolean isTraceDone() {
		boolean result = false;
		
		helperLock.acquire();
		
		if(tracersDone >= collectorThreads - helperThreads) {
			result = true;
		}
		if (Options.verbose.getValue() >= 5) {
			Log.prependThreadId();
			Log.write("isTraceDone - collectorThreads=");Log.write(collectorThreads);
			Log.write(", helperThreads=");Log.write(helperThreads);
			Log.write(", tracersDone=");Log.write(tracersDone);
			Log.write(", result=");Log.writeln(result);
		}
		
		helperLock.release();
		
		return result;
	}
	
	public void printPreStats() {
		super.printPreStats();
		Log.write("\n");
		Log.flush();
	}
	
	public static void acquireLogLock() {
		logLock.acquire();
	}
	
	public static void releaseLogLock() {
		logLock.release();
	}
}
